Continuing Our Exploration
In the challenge Did You Look Everywhere
from the previous session, you had to manually look for the .flag
file in the given hierarchy.
This, as you probably remember, was slow and frustrating.
We need some means by which to view an entire hierarchy at once, or to look for specific files.
Good news: such means do exist.
Tree of Life
The tree
command does what its name suggests: it displays a tree representation of a directory hierarchy.
root@kali:~# tree /lib/cryptsetup/
/lib/cryptsetup/
├── askpass
├── checks
│ ├── blkid
│ ├── ext2
│ ├── swap
│ ├── un_blkid
│ └── xfs
├── cryptdisks-functions
├── functions
└── scripts
├── decrypt_derived
├── decrypt_gnupg
├── decrypt_gnupg-sc
├── decrypt_keyctl
├── decrypt_opensc
├── decrypt_ssl
└── passdev
We can also specify a maximum depth for tree
's traversal.
Look up the parameter in tree
's man
page.
Once you've found it, use it in order to set a maximum depth of 2 for the /opt
folder.
Your output should look like this:
root@kali:~# tree <add your paramtere here> /opt/
/opt/
├── google
│ └── chrome
└── Teeth
├── cache
├── etc
├── housekeep
├── README.txt
├── static
└── units
Note that if you set the maximum depth to 1, tree
essentially becomes a fancier looking ls
.
Find Your Way
As far as exploration goes, tree
is a pretty strong tool.
It is capable of instantly displaying an entire file hierarchy.
But sometimes we have to deal with a large hierarchy in which we know what we're looking for.
As an example, let's look for the memc.h
header file of the Linux kernel.
It's a small file that defines a tiny part of the communication between the GPU and RAM.
The header files corresponding to the version of your kernel are located in /usr/src/linux-headers-5.6.0-kali2-common
.
Now go look for memc.h
.
Good luck!
You should find it by... tomorrow.
But don't look for it manually.
This would be stupid.
You know what the name of the file is and you know the folder where to find it.
That's a lot of information already.
There is a Linux command that's useful for looking for files with certain particularities (such as names, size, access rights etc.) in a file hierarchy.
Which is exactly what we need!
This command is called find
.
It outputs all files in a directory (and its subdirectories) that match some given properties.
Look up its syntax in the man
page.
Look for the string "EXAMPLES".
It should lead you to a section at the end of the man
page, which gives you a series of detailed examples of the command's usage.
From them and from the commands general description at the beginning of the man
page, we can see that find
is generally used like this:
root@kali:~# find <where to look> <what to look for>
<where to look>
is quite self-explanatory: it's a path in the file system, from wherefind
will start to look for what we told it. This means thatfind
traverse the file system down from the path it's given by looking exhaustively into each of its directories.<what to look for>
is where things get more interesting. We've already said that we can look for files that match certain properties, such as names, permissions, sizes, types and so on. Each of these properties can be specified as follows:
find
can take many more parameters, as described below.
type
This parameter allows us to select either regular files (-type f
), or directories (-type d
) and so on.
Here's an example:
find /some/random/path -type d
This command will list all subdirectories in /some/random/path
.
size
Filters files by size. For example, we can look for all files whose sizes are 100 bytes using this command
find /some/random/path -size 100c
The c
at the end specifies that we're using bytes as unit of measurement.
The size can also be specified in:
- kibibytes (1024 bytes) using the letter
k
; - mebibytes (1024 kibibytes) using the letter
M
; - gibibytes (1024 mebibytes) using the letter
G
;
But looking for files with exactly a given size seldom happens.
It is more often the case that we're looking for files smaller or larger than some value.
For this reason, we may use the -
(smaller) or +
signs before the actual size, like this:
find /some/random/path -size +100k # Find all files with a size larger than 100kB.
name
This is the simplest property.
It's for when you're looking for a file with a certain name.
For finding the file my_file
, it goes like this:
find /some/random/path -name my_file
This parameter is the one we need for our current task.
In order to find the file memc.h
, we'll run:
root@kali:~# find /usr/src/linux-headers-5.6.0-kali2-common/ -name memc.h
/usr/src/linux-headers-5.6.0-kali2-common/arch/arm/include/asm/hardware/memc.h
root@kali:~# cat /usr/src/linux-headers-5.6.0-kali2-common/arch/arm/include/asm/hardware/memc.h
/* SPDX-License-Identifier: GPL-2.0-only */
[...]
#endif
Globbing
But what if we only knew a portion of our filename?
Or what if we intentionally wanted to find all files ending in .log
, for instance?
For this, we need to use the concept of globbing. This mechanism defines a set of special characters that are interpreted differently than regular ASCII text.
*
: 0 or more characters
The *
character is interpreted as any number of characters of any type.
Basically, *
stands for everything and nothing.
Take a look at the example below:
root@kali:~# ls D*
Desktop:
Documents:
Downloads:
The *
makes ls
's parameter match any filename that starts with D
.
You can use *
anywhere in your parameter.
Moreover, any command that takes a filename as input accepts the globbing syntax.
Here's another example:
root@kali:~# find /usr/src/linux-headers-5.6.0-kali2-common -name *group* # List all linux kernel header files whose names contain the word "group"
/usr/src/linux-headers-5.6.0-kali2-common/arch/s390/include/asm/ccwgroup.h
[...]
/usr/src/linux-headers-5.6.0-kali2-common/include/uapi/linux/cgroupstats.h
The most commonly used globbing examples (apart from *
) are listed below.
+
: one or more characters
This symbol is very similar to *
.
However, +
needs at least one character in order to match the specific string.
Ranges
[]
represents a range.
Let's look at the following range: [a-f]
.
It starts at a
and ends with f
, inclusively.
[0-9]
, for example, matches any digit.
Likewise, [A-Z]
matches any capital letter, and so on.
You can combine ranges.
For instance, [a-zA-Z]
matches any letter.
Sets
In order to represent a set of characters to be matched, {}
are used.
{a,0,m,b}
matches either a
, 0
, m
or b
.
Escaping
What if we want to match the *
character itself?
Or any of the special characters above, such as +
, [
, ]
, {
and }
?
Obviously, we can match any character.
The special characters can be matched by escaping them.
Escaping is the practice of specifying that a symbol be interpreted as a regular character and not as part of a globbing expression.
This feature is achieved by placing a \
character before any special globbing character that you want to escape.
Here are a few examples:
\*
: matches a literal*
;\[
: matches a literal[
;{\+,\}}
: matches either a literal+
, or a literal}
.
You can combine escaped characters and globbing expressions as you please.
Redirecting Streams
Any process (remember that Linux commands are processes, too) uses 3 implicit data streams.
Standard Input (stdin
)
This is the "place" from where the process reads its data. Many processes read data from the keyboard:
man
'sstdin
is the keyboard because it reads user commands and interprets them in order to navigate the currentman
page.bash
'sstdin
is also the keyboard.bash
reads user input, that is typed into the terminal and then executes those commands.
The main alternative to getting input from the terminal is using a file.
Strictly from a teaching standpoint, unless given a file, cat
reads its input from the terminal (kinda useless, we know).
Let's showcase the usage of stdin
redirection.
First, let's use cat
without redirection.
root@kali:~# cat # Read input from the terminal.
SSS Rulz!
SSS Rulz!
^C
root@kali:~# # We used Ctrl + C to close the above cat process.
Now let's redirect cat
's input to a file.
We use <
in order to redirect stdin
.
root@kali:~# cat < essentials/README.md
# Security Summer School: Security Essentials Track
[...]
Let's look more closely at what happens here, as opposed to running cat essentials/README.md
:
cat essentials/README.md
makes thecat
command itself open theessentials/README.md
file and read bytes from it;cat < essentials/README.md
has the underlyingbash
process read theessentials/README.md
file and feed its content tocat
, which is now reading input from the underlyingbash
process.
The same output is printed, but the mechanism now differs entirely.
Standard Output (stdout
)
stdout
is the complement of stdin
.
A processes output is generally displayed to stdout
.
Generally, this stream is also the terminal.
We've already seen this feature when running almost any command so far.
root@kali:~# ls
Desktop essentials Music Public Videos
Documents ghidra_9.1.2_PUBLIC peda %SystemDrive%
Downloads libc-database Pictures Templates
We've seen this output may times in the previous session.
The names of the directories and files inside the current folder are printed to stdout
, i.e. to the terminal.
Let's redirect ls
's output to a file:
root@kali:~# ls > ls_output
Notice that now there is seemingly no output.
In reality, it does exist, but is written by ls
to the ls_output
file instead of the standard stdout
stream (the terminal).
root@kali:~# cat ls_output # The same files as before. Their layout changes, though.
Desktop
Documents
Downloads
essentials
ghidra_9.1.2_PUBLIC
libc-database
Music
out
peda
Pictures
Public
%SystemDrive%
Templates
Videos
Standard Error (stderr
)
Sometimes commands fail. If you haven't encountered one, you haven't been using Linux for long enough. Here's a simple error:
root@kali:~# ls whatever
ls: cannot access 'whatever': No such file or directory
The error message (ls: cannot access 'whatever': No such file or directory
) is displayed to the terminal, so it would make sense for it to be printed by ls
to stdout
, right?
Well... no.
It's printed to another stream, called stderr
.
As its name implies, this stream is dedicated to error messages.
This distinction was made in order for users to be able to separate between useful / legitimate output and sometimes unimportant error messages.
Redirecting stderr
is performed using 2 characters: 2>
.
root@kali:~# ls whatever 2> ls_error
root@kali:~# # No error message. The error itself still did happen.
root@kali:~# cat ls_error # I told you so...
ls: cannot access 'whatever': No such file or directory
Appending
Let's redirect ls
's output multiple times:
root@kali:~# ls essentials > out
root@kali:~# cat out
application-lifetime
assembly-language
binary-analysis
data-representation
data-security
explaining-the-internet
hacking-the-web
README.md
rediscovering-the-browser
system-exploration
taming-the-stack
welcome-to-linux
root@kali:~# ls > out
root@kali:~# cat out
Desktop
Documents
Downloads
essentials
ghidra_9.1.2_PUBLIC
libc-database
Music
out
peda
Pictures
Public
%SystemDrive%
Templates
Videos
As you can see, when the second ls
output is written to the out
file, the first output is overwritten.
This sucks in case we want to generate large output files, such as logs.
But fear not!
Instead, remember last session's Python crash course.
More specifically, remember open
's parameters.
The second one was mode
and one of the modes is append, symbolised by the a
character.
This mode makes any text that's written to that specific file to be added at the end of whatever data was already inside it, without overwriting anything.
We need something similar to that, which can be achieved by using >>
for redirecting stdin
and 2>>
for redirecting stderr
.
Here's how it works for the previous ls
commands:
root@kali:~# ls essentials > out # This command also creates the out file. It is irrelevant whether we use > or >>.
root@kali:~# cat out
application-lifetime
assembly-language
binary-analysis
data-representation
data-security
explaining-the-internet
hacking-the-web
README.md
rediscovering-the-browser
system-exploration
taming-the-stack
welcome-to-linux
root@kali:~# ls >> out # This is where the overwriting took place. We've now changed command to use >>.
root@kali:~# cat out
application-lifetime
[...]
Videos
Lovely!
Pipes
Up to now, we've looked at how to redirect the basic streams of a process to files.
But what if we wanted to redirect one stream of a process into the stream of another process?
Of course we can do this, too, by using the pipe (|
).
To demonstrate the usage of pipes, we'll introduces the tac
command.
Notice it's cat
in reverse.
This is not arbitrary.
If cat
displays the lines in a file in order, tac
does the same, but in reverse order.
Let's exemplify using the global README.md
:
root@kali:~# tac essentials/README.md
1. [Taming the Stack](./taming-the-stack)
1. [Assembly Language](./assembly-language)
1. [Binary Analysis](./binary-analysis)
1. [Application Lifetime](./application-lifetime)
1. [Data Security](./data-security)
1. [Data Representation](./data-representation)
1. [Hacking the Web](./hacking-the-web)
1. [(Re)Discovering the Browser](./rediscovering-the-browser)
1. [Explaining the Internet](./explaining-the-internet)
1. [System Exploration](./system-exploration)
1. [Welcome to Linux](./welcome-to-linux)
Sessions are:
There, you will find a `README.md` file with the session documentation and, if it's the case, subfolders with support data for the challenges.
Each session is located in its specific folder.
Welcome to the Security Essentials Track of the Security Summer School.
# Security Summer School: Security Essentials Track
Now let's actually use a pipe. We'll also use a command from before, so we keep things simple. Let's find all Linux kernel header files whose names contain the word "group" and then print them in reverse order:
find /usr/src/linux-headers-5.6.0-kali2-common -name *group* | tac
Notice this output is the one you saw in section *
: 0 or more characters, but in reverse.
xargs
We've seen how powerful pipes are.
Remember that pipes redirect the stdout
of the first command into the second's stdin
.
But what if we wanted to redirect the same stdout
as command-line parameters for the second command?
This is when we would use | xargs
.
Let's assume this hypothetical command:
cmd1 | xargs cmd2
where cmd1
and cmd2
are hypothetical commands.
The | xargs
keyword makes every line from the stdout
of cmd1
be passed as a separate parameter to cmd2
.
A very powerful use case of xargs
is find ... | xargs grep ...
.
Such commands allow us to look for strings in all files that match certain criteria at once.
Let's showcase this by inspecting all log files about the systemd
process:
# find /var/log -type f -name *log | xargs grep systemd
Less is More
Try running the following command:
root@kali:~# tree -L 2 /
You can see that its output is rather huge.
For large directory hierarchies, tree
's output can be overwhelming.
The same thing can happen when running find
commands.
We've already seen how we can trim such outputs down by grep
, but sometimes we have no criterion on which to do so.
In these situations, we have no alternative but to look at the entire output.
In order to do so in more easily, we can use the less
command.
First of all, less
can be used just like you would use cat
.
Run the commands below.
You can navigate inside less
the same way you can navigate inside man
.
And you definitely remember how to navigate inside man
!
root@kali:~# cat /var/log/syslog # Never ending logs...
[...]
root@kali:~# less /var/log/syslog # Much better.
Inside less
, try searching for the string "network".
Remember: Don't scroll your terminal.
It doesn't look cool.
Use less
!
Grep
find
has taught us how to use various criteria in order to filter through a file hierarchy.
This is definitely useful, but we can do better.
find
is highly capable of filtering output based on the files' metadata, i.e. "surface level" information, such as sizes, names and so on.
It would be really useful if we had a means to filter files based on their content.
And we do!
This tool is called grep
.
grep
is capable of matching strings based on the contents of files, not just on their names.
Let's look for the stdin
string in this README:
root@kali:~# grep "stdin" essentials/system-exploration/README.md
#### Standard Input (`stdin`)
- `man`'s `stdin` is the keyboard because it reads user commands and interprets them in order to navigate the current `man` page.
- `bash`'s `stdin` is also the keyboard.
Let's showcase the usage of `stdin` redirection.
We use `<` in order to redirect `stdin`.
`stdout` is the complement of `stdin`.
We need something similar to that, which can be achieved by using `>>` for redirecting `stdin` and `2>>` for redirecting `stderr`.
As you can see, grep
outputs the lines that contain the given string.
This makes grep
extremely useful for looking for CTF flags.
We simply need to grep "SSS"
.
Grep a File Hierarchy
At this point, you may be tempted to believe that we can only grep
a single file.
Nope.
We can even grep
strings in entire file hierarchies, which is extremely powerful.
Let's grep
the task_struct
symbol in our kernel's header files.
This symbol is a C struct
that contains all the information associated with any Linux process.
root@kali:~# grep -R "task_struct" /usr/src/linux-headers-5.6.0-kali2-common | less # The output is rather large, so we contain it with less.
Remember this distinction: find
looks for file metadata (names, permissions, size, type), while grep
looks for file data.